#include "primitive.hpp"

bool PrimSphere::Intersection ( Ray &ray, Vector &hit_point, Vector &normal )
{
    real_t r_2  = _radius * _radius;
    if ( (_position - ray.position).length2() - r_2 < 0.0)
        return false;
        
    real_t EB =  multiply( _position, ray.direction) - multiply( ray.direction, ray.position);
    if ( EB < 0.0)
    {
        return false;
    }
    
    Vector K = ray.position + ray.direction * EB;
        
    real_t KC_2 = ( K - _position).length2();
        
    if ( ( KC_2 -  r_2 ) > 0.0)
    {
        return false;
    }
    
    hit_point = K - ray.direction *  sqrt( r_2 - KC_2);
    normal    = _position - hit_point;
    normal.normalize();
    
    return true;
}
bool PrimSphere::IntersectionInner ( Ray &ray, Vector &hit_point, Vector &normal )
{
    return false;
}


bool PrimSphere::IsIntersected ( Ray &ray )
{
    real_t r_2  = _radius * _radius;
    if ( (_position - ray.position).length2() - r_2 < 0.0)
        return true;
    
    real_t EB =  multiply( _position, ray.direction) - multiply( ray.direction, ray.position);
    if ( EB < 0.0)
    {
        return false;
    }

    
    Vector K = ray.position + ray.direction * EB;


    real_t KC_2 = ( K - _position).length2();
    
    if ( ( KC_2 -  r_2 ) > 0.0)
    {
        return false;
    }
    return true;
    
}



void PrimSphere::SetPosition ( Vector &position )
{
    _position = position;
}
void PrimSphere::SetRadius ( real_t radius )
{
    _radius = radius;
}

PrimSphere::PrimSphere() {};
PrimSphere::~PrimSphere() {};

//////////////////// Triangle /////////////////////
bool PrimTriangle::Intersection ( Ray &ray, Vector &hit_point, Vector &normal )
{
    real_t mult_dir_norm = multiply( _normal,ray.direction);
    if ( mult_dir_norm < 0.0)
    {
        return false;
    }

    real_t ro; // расстоянее до плоскости
    ro = std::abs( ray.position.x() * _normal.x() + ray.position.y() * _normal.y() + ray.position.z() * _normal.z() + _D);

    
    hit_point = ray.position + ray.direction * ( ro / mult_dir_norm);
    normal    = _normal;
    ////////////// Внутри ли треугольника
    real_t S1 = ( ( hit_point - _p1) * ( hit_point - _p2)).length();
    real_t S2 = ( ( hit_point - _p2) * ( hit_point - _p3)).length();
    real_t S3 = ( ( hit_point - _p3) * ( hit_point - _p1)).length();

    if ( _S < ( S1 + S2 + S3 - _PRECISION_2))
    {
        return false;
    }else
    {
        return true;
    }
}

bool PrimTriangle::IntersectionInner ( Ray &ray, Vector &hit_point, Vector &normal )
{
    real_t mult_dir_norm = multiply( _normal,ray.direction);
    if ( mult_dir_norm > 0.0)
    {
        return false;
    }
    
    real_t ro; // расстоянее до плоскости
    ro = std::abs( ray.position.x() * _normal.x() + ray.position.y() * _normal.y() + ray.position.z() * _normal.z() + _D);
    
    
    hit_point = ray.position + ray.direction * ( ro / mult_dir_norm);
    normal    = _normal;
    ////////////// Внутри ли треугольника
    real_t S1 = ( ( hit_point - _p1) * ( hit_point - _p2)).length();
    real_t S2 = ( ( hit_point - _p2) * ( hit_point - _p3)).length();
    real_t S3 = ( ( hit_point - _p3) * ( hit_point - _p1)).length();
    
    if ( _S < ( S1 + S2 + S3 - _PRECISION_2) )
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool PrimTriangle::IsIntersected ( Ray &ray )
{
    real_t mult_dir_norm = multiply( _normal,ray.direction);
    if ( mult_dir_norm < 0.0)
    {
        return false;
    }
    
    
    real_t ro; // расстоянее до плоскости
    ro = std::abs( ray.position.x() * _normal.x() + ray.position.y() * _normal.y() + ray.position.z() * _normal.z() + _D);
    
    Vector hit_point = ray.position + ray.direction * ( ro / mult_dir_norm);
    ////////////// Внутри ли треугольника
    real_t S1 = ( ( hit_point - _p1) * ( hit_point - _p2)).length();
    real_t S2 = ( ( hit_point - _p2) * ( hit_point - _p3)).length();
    real_t S3 = ( ( hit_point - _p3) * ( hit_point - _p1)).length();
    
    if ( _S < ( S1 + S2 + S3))
    {
        return false;
    }else
    {
        return true;
    }
}

void PrimTriangle::SetPoints ( Vector &p1, Vector &p2, Vector &p3 )
{
    _p1 = p1;
    _p2 = p2;
    _p3 = p3;
    
    _normal = ( p3 - p1 ) * ( p2 - p1);
    _S = _normal.length();
    _normal.normalize();

    _D = - ( _normal.x() * p1.x() + _normal.y() * p1.y() + _normal.z() * p1.z() );
}

PrimTriangle::PrimTriangle(){}
PrimTriangle::~PrimTriangle(){}
///////////////////////////////////////////////////////////

///// TOR


namespace func
{
    inline real_t f( real_t x,real_t y,real_t z, real_t inner, real_t outer)
    {
        real_t x_2 = x * x;
        real_t y_2 = y * y;
        real_t z_2 = z * z;

/*        real_t r_2 = ( outer - inner)/2;
        r_2 *= r_2;
        real_t R_2 = ( outer + inner)/2;
        R_2 *= R_2;
*/        
//        real_t tmp1 = ( x_2 + y_2 + z_2 + R_2 - r_2);
//        real_t tmp2 = 4 * R_2 * ( x_2 + y_2);


        real_t a = inner;
        real_t r = ( outer - inner)/2;
        real_t r_2 = r * r;

        real_t tmp = ( a + r - std::sqrt( x_2 + y_2 ));
        
        return ( tmp * tmp + z_2 - r_2);
        //return x_2 + y_2 + z_2 - a*a;
    }
}

bool PrimTor::Intersection       ( Ray &ray, Vector &hit_point, Vector &hit_normal )
{
    Vector plain_normal = ray.direction * _normal;
    plain_normal.normalize();

    real_t plain_D = - ( plain_normal.x() * ray.position.x() + plain_normal.y() * ray.position.y() + plain_normal.z() * ray.position.z() );
    
    real_t ro; // расстоянее до плоскости
    ro = std::abs( _position.x() * _normal.x() + _position.y() * _normal.y() + _position.z() * _normal.z() + plain_D);
    if ( ro > _radius_outer + _PRECISION)
    {
      //  return false;
    }
    /////////////////
    real_t t = 0;
    
    real_t r = ( _radius_outer - _radius_inner)/2;    
    real_t p = std::abs( r - ro);

    
    //real_t delta_t = ( 0.1/r - 0.5) * p + r/2;
    real_t delta_t = ( _radius_outer - _radius_inner)/8;

    //real_t delta_t = 0.1;
    
    if ( delta_t < 0.0)
    {
        return false;
    }
    
    real_t x,y,z;
    real_t a,b,c;

    
    
    bool Intersceted = false;
    bool first = true;
    bool sign;
    
    while ( t < config.scene_size && !Intersceted)
    {
        x = ray.position.x() + ray.direction.x() * t - _position.x();
        y = ray.position.y() + ray.direction.y() * t - _position.y();
        z = ray.position.z() + ray.direction.z() * t - _position.z();

        if ( first)
        {
            real_t f_r = func::f( x,y,z,_radius_inner, _radius_outer);
            if ( f_r  < 0.0)
            {
                sign = false;
            }
            else
            {
                sign = true;
            }
            first = false;
        }
        else
        {
            real_t  f_r =  func::f( x,y,z,_radius_inner, _radius_outer);
            if ( ( ( f_r  < 0.0)  && sign) || ( ( f_r > 0.0) && !sign))
            {
                a = t - delta_t;
                b = t;
                Intersceted = true;
            }
        }
        t += delta_t;
    }
    if ( !Intersceted)
    {
        return false;
    }
    
    
    
    real_t x_a;
    real_t y_a;
    real_t z_a;
    
    real_t x_c;
    real_t y_c;
    real_t z_c;
    
    
    while ( ( b - a) > config.precision)
    {
        c = ( a + b)/2;
        
        x_a = ray.position.x() + ray.direction.x() * a - _position.x();
        y_a = ray.position.y() + ray.direction.y() * a - _position.y();
        z_a = ray.position.z() + ray.direction.z() * a - _position.z();

        x_c = ray.position.x() + ray.direction.x() * c;
        y_c = ray.position.y() + ray.direction.y() * c;
        z_c = ray.position.z() + ray.direction.z() * c;

        if ( func::f( x_a,y_a,z_a,_radius_inner, _radius_outer) * func::f( x_c,y_c,z_c,_radius_inner, _radius_outer) < 0.0)
        {
            b = c;
        }
        else
        {
            a = c;
        }

    }

    hit_point.xyz( x_c, y_c , z_c);


    Vector n( 0.0, 0.0 , 1.0);

    Vector OA = hit_point - _position;
    OA.normalize();

    Vector OO1 = OA - n * multiply( n, OA);
    OO1.normalize();
    
    Vector O1 = _position + OO1 * ( ( _radius_outer + _radius_inner)/2);
    
    
    hit_normal = O1  - hit_point;// - O1;
    hit_normal.normalize();
    
    return true;
}
bool PrimTor::IntersectionInner  ( Ray &ray, Vector &hit_point, Vector &normal )
{
    return false;
}
bool PrimTor::IsIntersected ( Ray &ray)
{
    Vector tmp1,tmp2;
    return Intersection( ray, tmp1, tmp2 );
}
////
void PrimTor::SetRadius ( real_t inner, real_t outer )
{
    _radius_inner = inner;
    _radius_outer = outer;
}

void PrimTor::SetPosition ( Vector &position )
{
    _position = position;
}

void PrimTor::SetNormal ( Vector &normal )
{
    _normal = normal;
    _normal.normalize();
}

PrimTor::PrimTor(){}
PrimTor::~PrimTor(){}
